﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.IO;
using System.Threading;

namespace ICeFramework.Utilities.TCPComm
{
    public class TCPServer
    {
        private TcpListener tcpListener;
        private Thread listenThread;
        int ClientCount = 0;

        public event ClientRequestHandler ClientRequest;
        public event ClientConnectHandler ClientConnect;

        public TCPServer(int port)
        {
            tcpListener = new TcpListener(IPAddress.Any, port);
            listenThread = new Thread(new ThreadStart(ListenForClients));
            listenThread.Start();
        }

        private void ListenForClients()
        {
            tcpListener.Start();
            while (true)
            {
                //blocks until a client has connected to the server
                TcpClient client = tcpListener.AcceptTcpClient();
                OnClientConnect(new ClientConnectEventArgs(client));
                
                Thread clientThread = new Thread(new ParameterizedThreadStart(HandleClientComm));
                clientThread.Start(client);
            }
        }

        private void HandleClientComm(object client)
        {
            ClientCount++;
            TcpClient[] arClient = new TcpClient[30];

            TcpClient tcpClient = (TcpClient)client;
            arClient[ClientCount] = tcpClient;
            NetworkStream clientStream = arClient[ClientCount].GetStream();

            byte[] message = new byte[4096];
            int bytesRead;

            bool success = true;
            Exception error = null;
            while (true)
            {
                bytesRead = 0;

                try
                {
                    //blocks until a client sends a message
                    bytesRead = clientStream.Read(message, 0, 1024);                    
                }
                catch (Exception ex)
                {
                    //a socket error has occured
                    error = ex;
                    success = false;
                    break;
                }

                if (bytesRead == 0)
                {
                    //the client has disconnected from the server
                    break;
                }
                if (success)
                {
                    try
                    {
                        TCPRequest req = (TCPRequest)Serialization.BinarySerialization.GetObject(message);
                        object responseObject = OnClientRequest(new ClientRequestEventArgs(req));

                        TCPResponse response = new TCPResponse(ServerResponseStatus.Ok, ServerResponseType.Data, responseObject);
                        byte[] sendBytes = Serialization.BinarySerialization.GetBytes(response);
                        clientStream.Write(sendBytes, 0, sendBytes.Length);
                    }
                    catch (Exception ex)
                    {
                        TCPResponse response = new TCPResponse(ServerResponseStatus.Error, ServerResponseType.Error, (object)ex);
                        byte[] sendBytes = Serialization.BinarySerialization.GetBytes(response);
                        clientStream.Write(sendBytes, 0, sendBytes.Length);
                    }
                }
                else
                {
                    TCPResponse response = new TCPResponse(ServerResponseStatus.Error, ServerResponseType.Error, (object)error);
                    byte[] sendBytes = Serialization.BinarySerialization.GetBytes(response);
                    clientStream.Write(sendBytes, 0, sendBytes.Length);
                }
            }
            arClient[ClientCount].Close();
        }

        public virtual object OnClientRequest(ClientRequestEventArgs e)
        {
            if (ClientRequest != null)
            {
                return ClientRequest(this, e);
            }
            return null;
        }

        public virtual void OnClientConnect(ClientConnectEventArgs e)
        {
            if (ClientConnect != null)
            {
                ClientConnect(this, e);
            }
        }
    }
}
